/**
 *    Copyright (C) 2009 Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */

#ifndef _CUBEFS_CORE_API_METADATA_PAGE_HPP
#define _CUBEFS_CORE_API_METADATA_PAGE_HPP

#include <cubefs/api/metadata/page.hpp>
#include <cubefs/core/api/metadata/group.hpp>
#include <cubefs/core/api/metadata/field.hpp>
#include <cubefs/core/api/metadata/page_wrapper.hpp>

#define CUBEFS_SOURCE "cubefs/core/api/metadata/page.hpp:" CUBEFS_AT

namespace cubefs { namespace api {


template <typename Interface>
class PageMetadataImplT: public MetadataGroupImplT<Interface> {
	typedef PageMetadataImplT<Interface> 	Me;
	typedef MetadataGroupImplT<Interface> 	Base;

public:

    PageMetadataImplT(StringRef name, const MetadataList &content, Int attributes, Int hash0, PageSizeProviderFn page_size_provider, Int page_size);

    virtual Int Hash() {
        return hash_;
    }

    virtual bool IsAbiCompatible() {
        return abi_compatible_;
    }

    virtual Int GetPageSize() {
    	return page_size_;
    }

    virtual void Externalize(const void *mem, void *buf);

    virtual void Internalize(const void *buf, void *mem, Int size);

    virtual Int GetDataBlockSize(const void* buf) {
    	return (*page_size_provider_)(buf);
    }

    virtual Int GetPageDataBlockSize(Page* tpage) {
    	Page* page = static_cast<Page*>(tpage);

    	if (page == NULL) {
    		throw NullPointerException(CUBEFS_SOURCE, "Page must not be null");
    	}

    	return (*page_size_provider_)(page->Ptr());
    }

    virtual FieldMetadata* GetField(Int ptr, bool abi);

    static void Init() {
        AbstractType *type = new AbstractType(TypeNameFactory<Interface>::name(), MetadataGroup::MyType());
        type_ = type;

        type->AddOperation(new BasicOperation<Int (Me::*)()>(type, "Hash", &Me::Hash));
        type->AddOperation(new BasicOperation<bool (Me::*)()>(type, "IsAbiCompatible", &Me::IsAbiCompatible));

        type->AddOperation(new BasicOperation<Int (Me::*)(Page*)>(type, "GetPageDataBlockSize", "page", &Me::GetPageDataBlockSize));
        type->AddOperation(new BasicOperation<Typed* (Me::*)(Int, bool), FieldMetadata* (Me::*)(Int, bool)>(type, FieldMetadata::MyType(), "GetField", "ptr", "abi", &Me::GetField));

        type->AddOperation(new BasicOperation<void (Me::*)(const void*, void*)>(type, "Externalize", "mem", "buf", &Me::Externalize));
        type->AddOperation(new BasicOperation<void (Me::*)(const void*, void*, Int)>(type, "Internalize", "mem", "buf", "size", &Me::Internalize));
    }

    CUBEFS_DECLARE_REFLECTION;

private:
    Int  hash_;
    bool abi_compatible_;
    bool attributes_;
    Int page_size_;
    PageSizeProviderFn page_size_provider_;
    PtrMap ptr_map_;
    PtrMap abi_map_;
};

template <typename Interface>
Type* PageMetadataImplT<Interface>::type_ 					= NULL;

typedef PageMetadataImplT<PageMetadata> 					PageMetadataImpl;



class ComputeHashFn {
    Int hash_;
    bool abi_compatible_;
public:

    ComputeHashFn(Int hash, bool abi_compatible) : hash_(hash), abi_compatible_(abi_compatible) {
    }

    void operator()(FieldMetadata *field) {
        if (field->GetTypeCode() != Metadata::BITMAP) {
            hash_ = (field->AbiPtr() * 1211) ^ CShr(hash_, 3) ^ (field->GetTypeCode() + 16 * field->Count() + field->Offset() * 256 + field->Limit() * 65536);
        }

        if (abi_compatible_) {
            abi_compatible_ = field->AbiPtr() == field->Ptr();
        }
    }

    Int hash() const {
        return hash_;
    }

    bool abi_compatible() {
        return abi_compatible_;
    }
};

class BuildPtrMapFn {
    PtrMap& ptr_map_;
    PtrMap& abi_map_;
public:

    BuildPtrMapFn(PtrMap& ptr_map, PtrMap& abi_map): ptr_map_(ptr_map), abi_map_(abi_map)  {}

    void operator()(FieldMetadata *field)
    {
        ptr_map_[(Int)field->Ptr()] = field;
        abi_map_[(Int)field->AbiPtr()] = field;
    }
};

template <bool Internalize_>
class InternalizeExternalizeFn {
    const void *src_;
    void *tgt_;

public:

    InternalizeExternalizeFn(const void *src, void *tgt) :
        src_(src), tgt_(tgt) {}

    void operator()(FieldMetadata *field)
    {
        if (Internalize_)
        {
            if (field->GetTypeCode() == Metadata::BITMAP || field->GetTypeCode() == Metadata::FLAG)
            {
                const char* csrc = static_cast<const char*> (field->BitmapPtrAbi(src_));
                char* ctgt = static_cast<char*> (field->BitmapPtr(tgt_));
                cubefs::tools::CopyBits(csrc, ctgt, field->Offset(), field->Offset(), field->Count());
            }
            else
            {
                for (Int c = 0; c < field->Count(); c++) {
                    field->SetValue(tgt_, field->ValuePtrAbi(src_, c), c);
                }
            }
        }
        else
        {
            if (field->GetTypeCode() == Metadata::BITMAP || field->GetTypeCode() == Metadata::FLAG)
            {
                const char* csrc = static_cast<const char*> (field->BitmapPtr(src_));
                char* ctgt = static_cast<char*> (field->BitmapPtrAbi(tgt_));

                cubefs::tools::CopyBits(csrc, ctgt, field->Offset(), field->Offset(), field->Count());
            }
            else
            {
                for (Int c = 0; c < field->Count(); c++)
                {
                    const void* ptr = field->ValuePtr(src_, c);
                    field->SetValueAbi(tgt_, ptr, c);
                }
            }
        }
    }
};

template <typename Functor>
bool ForAllFields(MetadataGroup *group, Functor &functor, Int limit) {
    for (Int c = 0; c < group->Size(); c++) {
        if (group->GetItem(c)->GetTypeCode() == Metadata::GROUP) {
            if (ForAllFields(static_cast<MetadataGroup*> (group->GetItem(c)), functor, limit)) {
                return true;
            }
        }
        else {
            FieldMetadata* field = static_cast<FieldMetadata*> (group->GetItem(c));

            if (field->Ptr() < limit) {
                functor(field);
            }
            else return true; // limit is reached
        }
    }

    return false;
}

template <typename Interface>
PageMetadataImplT<Interface>::PageMetadataImplT(StringRef name, const MetadataList &content, Int attributes, Int hash0, PageSizeProviderFn page_size_provider, Int page_size):
    Base(name, content), attributes_(attributes)
{
    Base::set_type() = Metadata::PAGE;
    abi_compatible_ = true;
    hash_ = hash0;
    page_size_provider_ = page_size_provider;
    page_size_ = page_size;

    if (page_size_provider_ == NULL) {
        throw NullPointerException(CUBEFS_SOURCE, "Page size provider is not specified");
    }

    ComputeHashFn fn(hash_, abi_compatible_);

    ForAllFields(this, fn, page_size);

    hash_ = fn.hash();
    abi_compatible_ = fn.abi_compatible();

    BuildPtrMapFn build_fn(ptr_map_, abi_map_);

    ForAllFields(this, build_fn, page_size);
}


template <typename Interface>
void PageMetadataImplT<Interface>::Externalize(const void *mem, void *buf) {
    Int ptr = GetDataBlockSize(mem);
    InternalizeExternalizeFn < false > fn(mem, buf);
    ForAllFields(this, fn, ptr);
}

template <typename Interface>
void PageMetadataImplT<Interface>::Internalize(const void *buf, void *mem, Int size) {
    if (size == -1) size = GetPageSize();
    InternalizeExternalizeFn < true > fn(buf, mem);
    ForAllFields(this, fn, size);
}


template <typename Interface>
FieldMetadata* PageMetadataImplT<Interface>::GetField(Int ptr, bool abi)
{
    PtrMap* map = abi ? &abi_map_ : &ptr_map_;
    PtrMap::iterator i = map->find(ptr);

    if (i != map->end()) {
        return i->second;
    }
    else {
        //throw CubefsException(CUBEFS_SOURCE, "Can't find field for the specified field ptr");
        return NULL;
    }
}


}}

#undef CUBEFS_SOURCE
#endif
